package com.gframework.mybatis.dao;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Supplier;

import org.apache.commons.lang3.ArrayUtils;
import org.springframework.util.Assert;

import com.gframework.util.GUtils;

import sun.misc.Unsafe;

/**
 * 自动设置属性值的dao拦截器.
 * <p>
 * 本类主要用在当发生保存/更新操作时，自动设置相关字段，甚至也可以用于自动填充主键值。
 * <br>
 * <strong>不过需要特别注意的是，本类会直接对属性进行修改，不会去调用setter、getter方法，并且默认只有在该属性无值的时候，才会进行填充（可通过配置指定此策略是否有效）</strong>。
 * <p>
 * 但是本类默认是没有初始化的，他需要用使用者自己进行bean的创建操作。
 * 
 * @since 2.0.0
 * @author Ghwolf
 */
@SuppressWarnings("restriction")
public class AutoSetFieldDaoInterceptor implements DaoSaveInterceptor, DaoUpdateInterceptor {
	/**
	 * 一个用来表示无需进行拦截处理的空的AutoSetFields类对象
	 */
	private static final AutoSetFields NO_HANDLE_SET_CONFIG = new AutoSetFields(null, null, null);

	/**
	 * 自动填充值规则配置集合
	 */
	private final List<SetRule> setRules;
	/**
	 * 存储保存操作需要自动设置之的逻辑缓存集合
	 */
	private final Map<Class<?>, AutoSetFields> autoSetOnSave = new ConcurrentHashMap<>();

	/**
	 * 存储更新操作需要自动设置之的逻辑缓存集合
	 */
	private final Map<Class<?>, AutoSetFields> autoSetOnUpdate = new ConcurrentHashMap<>();

	private AutoSetFieldDaoInterceptor(List<SetRule> setRules) {
		Assert.notNull(setRules,"setRules 不能为null！");
		this.setRules = setRules;
	}

	/**
	 * 调用此方法来构造一个配置类，而后通过Config配置类来构造AutoSetFieldDaoInterceptor类对象
	 */
	public static Config custom() {
		return new Config();
	}
	
	/**
	 * AutoSetFieldDaoInterceptor 构造配置类
	 * @since 2.0.0
	 * @author Ghwolf
	 */
	public static final class Config {
		/**
		 * 规则集合
		 */
		private List<SetRule> rules = new ArrayList<>();
		
		Config(){}
		
		/**
		 * 结束自定义配置并构造出一个AutoSetFieldDaoInterceptor类对象
		 * @return 返回 AutoSetFieldDaoInterceptor 类实例化对象
		 */
		public AutoSetFieldDaoInterceptor build(){
			return new AutoSetFieldDaoInterceptor(Collections.unmodifiableList(new ArrayList<>(rules)));
		}
		
		// save
		
		/**
		 * 设置一个在执行保存操作时，自动设置pojo属性的规则.
		 * 
		 * @param fieldName pojo属性名称，不能为null
		 * @param fieldType 该属性类型，不能为null
		 * @param value 要设置的值，可以为null
		 * @return 返回当前对象
		 * 
		 * @throws IllegalArgumentException 如果fieldName或fieldType参数为null
		 */
		public Config autoSetWhenSave(String fieldName,Class<?> fieldType,Object value){
			rules.add(new SetRule(true,fieldType,fieldName,new SimpleValueGetter(value)));
			return this; 
		}
		
		/**
		 * 设置一个在执行保存操作时，自动设置pojo属性的规则.
		 * 
		 * @param fieldName pojo属性名称，不能为null
		 * @param fieldType 该属性类型，不能为null
		 * @param supplier 一个用来获取要设置的指的供给型接口对象
		 * @return 返回当前对象
		 * 
		 * @throws IllegalArgumentException 如果任意参数为null
		 */
		public Config autoSetWhenSave(String fieldName,Class<?> fieldType,Supplier<?> supplier){
			Assert.notNull(supplier,"supplier不能为null！");
			rules.add(new SetRule(true,fieldType,fieldName,new SupplierValueGetter(supplier)));
			return this; 
		}
		
		/**
		 * 设置一个在执行保存操作时，自动设置pojo属性的规则.
		 * 
		 * @param fieldName pojo属性名称，不能为null
		 * @param fieldType 该属性类型，不能为null
		 * @param function 一个用来获取要设置的指的函数式接口对象，传参为pojo对象（注意不是集合，而是具体的一个pojo对象）
		 * @return 返回当前对象
		 * 
		 * @throws IllegalArgumentException 如果任意参数为null
		 */
		public Config autoSetWhenSave(String fieldName,Class<?> fieldType,Function<Object,?> function){
			Assert.notNull(function,"function不能为null！");
			rules.add(new SetRule(true,fieldType,fieldName,new FunctionValueGetter(function)));
			return this; 
		}
		
		/**
		 * 设置一个在执行保存操作时，自动设置pojo属性的规则.
		 * 
		 * @param fieldName pojo属性名称，不能为null
		 * @param fieldType 该属性类型，不能为null
		 * @param value 要设置的值，可以为null
		 * @param ignoreIfNotNull 是否仅在值为null时才进行赋值，默认true
		 * @return 返回当前对象
		 * 
		 * @throws IllegalArgumentException 如果fieldName或fieldType参数为null
		 */
		public Config autoSetWhenSave(String fieldName,Class<?> fieldType,Object value,boolean ignoreIfNotNull){
			rules.add(new SetRule(true,fieldType,fieldName,new SimpleValueGetter(value),ignoreIfNotNull));
			return this; 
		}
		
		/**
		 * 设置一个在执行保存操作时，自动设置pojo属性的规则.
		 * 
		 * @param fieldName pojo属性名称，不能为null
		 * @param fieldType 该属性类型，不能为null
		 * @param supplier 一个用来获取要设置的指的供给型接口对象
		 * @param ignoreIfNotNull 是否仅在值为null时才进行赋值，默认true
		 * @return 返回当前对象
		 * 
		 * @throws IllegalArgumentException 如果任意参数为null
		 */
		public Config autoSetWhenSave(String fieldName,Class<?> fieldType,Supplier<?> supplier,boolean ignoreIfNotNull){
			Assert.notNull(supplier,"supplier不能为null！");
			rules.add(new SetRule(true,fieldType,fieldName,new SupplierValueGetter(supplier),ignoreIfNotNull));
			return this; 
		}
		
		/**
		 * 设置一个在执行保存操作时，自动设置pojo属性的规则.
		 * 
		 * @param fieldName pojo属性名称，不能为null
		 * @param fieldType 该属性类型，不能为null
		 * @param function 一个用来获取要设置的指的函数式接口对象，传参为pojo对象（注意不是集合，而是具体的一个pojo对象）
		 * @param ignoreIfNotNull 是否仅在值为null时才进行赋值，默认true
		 * @return 返回当前对象
		 * 
		 * @throws IllegalArgumentException 如果任意参数为null
		 */
		public Config autoSetWhenSave(String fieldName,Class<?> fieldType,Function<Object,?> function,boolean ignoreIfNotNull){
			Assert.notNull(function,"function不能为null！");
			rules.add(new SetRule(true,fieldType,fieldName,new FunctionValueGetter(function),ignoreIfNotNull));
			return this; 
		}
		
		// update
		
		/**
		 * 设置一个在执行更新操作时，自动设置pojo属性的规则.
		 * 
		 * @param fieldName pojo属性名称，不能为null
		 * @param fieldType 该属性类型，不能为null
		 * @param value 要设置的值，可以为null
		 * @return 返回当前对象
		 * 
		 * @throws IllegalArgumentException 如果fieldName或fieldType参数为null
		 */
		public Config autoSetWhenUpdate(String fieldName,Class<?> fieldType,Object value){
			rules.add(new SetRule(false,fieldType,fieldName,new SimpleValueGetter(value)));
			return this; 
		}
		
		/**
		 * 设置一个在执行更新操作时，自动设置pojo属性的规则.
		 * 
		 * @param fieldName pojo属性名称，不能为null
		 * @param fieldType 该属性类型，不能为null
		 * @param supplier 一个用来获取要设置的指的供给型接口对象
		 * @return 返回当前对象
		 * 
		 * @throws IllegalArgumentException 如果任意参数为null
		 */
		public Config autoSetWhenUpdate(String fieldName,Class<?> fieldType,Supplier<?> supplier){
			Assert.notNull(supplier,"supplier不能为null！");
			rules.add(new SetRule(false,fieldType,fieldName,new SupplierValueGetter(supplier)));
			return this; 
		}
		
		/**
		 * 设置一个在执行更新操作时，自动设置pojo属性的规则.
		 * 
		 * @param fieldName pojo属性名称，不能为null
		 * @param fieldType 该属性类型，不能为null
		 * @param function 一个用来获取要设置的指的函数式接口对象，传参为pojo对象（注意不是集合，而是具体的一个pojo对象）
		 * @return 返回当前对象
		 * 
		 * @throws IllegalArgumentException 如果任意参数为null
		 */
		public Config autoSetWhenUpdate(String fieldName,Class<?> fieldType,Function<Object,?> function){
			Assert.notNull(function,"function不能为null！");
			rules.add(new SetRule(false,fieldType,fieldName,new FunctionValueGetter(function)));
			return this; 
		}
		
		/**
		 * 设置一个在执行更新操作时，自动设置pojo属性的规则.
		 * 
		 * @param fieldName pojo属性名称，不能为null
		 * @param fieldType 该属性类型，不能为null
		 * @param value 要设置的值，可以为null
		 * @param ignoreIfNotNull 是否仅在值为null时才进行赋值，默认true
		 * @return 返回当前对象
		 * 
		 * @throws IllegalArgumentException 如果fieldName或fieldType参数为null
		 */
		public Config autoSetWhenUpdate(String fieldName,Class<?> fieldType,Object value,boolean ignoreIfNotNull){
			rules.add(new SetRule(false,fieldType,fieldName,new SimpleValueGetter(value),ignoreIfNotNull));
			return this; 
		}
		
		/**
		 * 设置一个在执行更新操作时，自动设置pojo属性的规则.
		 * 
		 * @param fieldName pojo属性名称，不能为null
		 * @param fieldType 该属性类型，不能为null
		 * @param supplier 一个用来获取要设置的指的供给型接口对象
		 * @param ignoreIfNotNull 是否仅在值为null时才进行赋值，默认true
		 * @return 返回当前对象
		 * 
		 * @throws IllegalArgumentException 如果任意参数为null
		 */
		public Config autoSetWhenUpdate(String fieldName,Class<?> fieldType,Supplier<?> supplier,boolean ignoreIfNotNull){
			Assert.notNull(supplier,"supplier不能为null！");
			rules.add(new SetRule(false,fieldType,fieldName,new SupplierValueGetter(supplier),ignoreIfNotNull));
			return this; 
		}
		
		/**
		 * 设置一个在执行更新操作时，自动设置pojo属性的规则.
		 * 
		 * @param fieldName pojo属性名称，不能为null
		 * @param fieldType 该属性类型，不能为null
		 * @param function 一个用来获取要设置的指的函数式接口对象，传参为pojo对象（注意不是集合，而是具体的一个pojo对象）
		 * @param ignoreIfNotNull 是否仅在值为null时才进行赋值，默认true
		 * @return 返回当前对象
		 * 
		 * @throws IllegalArgumentException 如果任意参数为null
		 */
		public Config autoSetWhenUpdate(String fieldName,Class<?> fieldType,Function<Object,?> function,boolean ignoreIfNotNull){
			Assert.notNull(function,"function不能为null！");
			rules.add(new SetRule(false,fieldType,fieldName,new FunctionValueGetter(function),ignoreIfNotNull));
			return this; 
		}
		
		
	}
	
	// intercept
	
	@Override
	public void interceptUpdate(Class<?> pojoType, Object obj) {
		Unsafe unsafe = GUtils.getUnsafe();
		AutoSetFields vo = autoSetOnUpdate.computeIfAbsent(pojoType, this::initUpdate);
		for (int x = 0; x < vo.fieldOffsets.length; x ++) {
			if (vo.ignoreIfNotNull[x]) {
				if (unsafe.getObject(obj, vo.fieldOffsets[x]) == null) {
					unsafe.putObject(obj, vo.fieldOffsets[x], vo.valueGetters[x].get(obj));
				}
			} else {
				unsafe.putObject(obj, vo.fieldOffsets[x], vo.valueGetters[x].get(obj));
			}
		}
	}

	@Override
	public boolean canInterceptUpdate(Class<?> pojoType) {
		return autoSetOnUpdate.computeIfAbsent(pojoType, this::initUpdate) != NO_HANDLE_SET_CONFIG;
	}

	@Override
	public void interceptSave(Class<?> pojoType, Object obj) {
		Unsafe unsafe = GUtils.getUnsafe();
		AutoSetFields vo = autoSetOnSave.computeIfAbsent(pojoType, this::initSave);
		for (int x = 0; x < vo.fieldOffsets.length; x ++) {
			if (vo.ignoreIfNotNull[x]) {
				if (unsafe.getObject(obj, vo.fieldOffsets[x]) == null) {
					unsafe.putObject(obj, vo.fieldOffsets[x], vo.valueGetters[x].get(obj));
				}
			} else {
				unsafe.putObject(obj, vo.fieldOffsets[x], vo.valueGetters[x].get(obj));
			}
		}
	}

	@Override
	public boolean canInterceptSave(Class<?> pojoType) {
		return autoSetOnSave.computeIfAbsent(pojoType, this::initSave) != NO_HANDLE_SET_CONFIG;
	}

	/**
	 * 初始化 AutoSetFields
	 */
	private AutoSetFields init(boolean doSave, Class<?> pojoType) {
		Unsafe unsafe = GUtils.getUnsafe();
		List<Long> fieldOffsets = new ArrayList<>();
		List<ValueGetter> valueGetters = new ArrayList<>();
		List<Boolean> ignoreIfNotNull = new ArrayList<>();
		setRules.forEach(c -> {
			if (doSave ^ c.usedInSaveMethod) {
				return;
			}
			Field f = c.findField(pojoType);
			if (f != null) {
				fieldOffsets.add(unsafe.objectFieldOffset(f));
				valueGetters.add(c.valueGetter);
				ignoreIfNotNull.add(c.ignoreIfotNull);
			}
		});
		if (fieldOffsets.isEmpty()) {
			return NO_HANDLE_SET_CONFIG;
		} else {
			return new AutoSetFields(
					ArrayUtils.toPrimitive(fieldOffsets.toArray(new Long[fieldOffsets.size()])),
					valueGetters.toArray(new ValueGetter[valueGetters.size()]),
					ArrayUtils.toPrimitive(ignoreIfNotNull.toArray(new Boolean[ignoreIfNotNull.size()]))
					);
		}
	}

	private AutoSetFields initSave(Class<?> pojoType) {
		return init(true, pojoType);
	}

	private AutoSetFields initUpdate(Class<?> pojoType) {
		return init(false, pojoType);
	}

	/**
	 * 运行时会动态创建本类对象，将SetRule根据不同的pojoType转换为本类对象
	 */
	private static class AutoSetFields {

		/**
		 * 属性偏移集合
		 */
		final long[] fieldOffsets;
		/**
		 * 属性值获取对象集合，长度和fieldOffsets一样且一一对应。
		 */
		final ValueGetter[] valueGetters;
		/**
		 * 是否仅在无值时才进行设置
		 */
		final boolean[] ignoreIfNotNull ;

		// param can not be null
		public AutoSetFields(long[] fieldOffsets, ValueGetter[] valueGetters,boolean[] ignoreIfNotNull) {
			this.fieldOffsets = fieldOffsets;
			this.valueGetters = valueGetters;
			this.ignoreIfNotNull = ignoreIfNotNull;
		}

	}

	/**
	 * 自动填充值规则配置类
	 */
	private static class SetRule {

		// field value can not be null

		final boolean usedInSaveMethod;
		final Class<?> fieldType;
		final String fieldName;
		final ValueGetter valueGetter;
		/**
		 * 是否在已有值时不进行设置
		 */
		final boolean ignoreIfotNull ;
		
		public SetRule(boolean usedInSaveMethod, Class<?> fieldType, String fieldName, ValueGetter valueGetter) {
			this(usedInSaveMethod,fieldType,fieldName,valueGetter,true);
		}
		
		public SetRule(boolean usedInSaveMethod, Class<?> fieldType, String fieldName, ValueGetter valueGetter, boolean ignoreIfotNull) {
			Assert.notNull(fieldType,"fieldName不能为null");
			Assert.notNull(fieldType,"fieldType不能为null");
			this.usedInSaveMethod = usedInSaveMethod;
			this.fieldType = fieldType;
			this.fieldName = fieldName;
			this.valueGetter = valueGetter;
			this.ignoreIfotNull = ignoreIfotNull;
		}
		
		

		public Field findField(Class<?> type) {
			Class<?> cls = type;
			while (cls != Object.class) {
				for (Field f : type.getDeclaredFields()) {
					if (f.getName().equals(this.fieldName) && fieldType == f.getType()) {
						return f;
					}
				}
				cls = cls.getSuperclass();
			}
			return null;
		}
	}

	/**
	 * 值获取接口
	 */
	private static interface ValueGetter {

		public Object get(Object target);
	}

	private static class SimpleValueGetter implements ValueGetter {

		private final Object obj;

		public SimpleValueGetter(Object obj) {
			this.obj = obj;
		}

		@Override
		public final Object get(Object target) {
			return obj;
		}

	}

	private static class SupplierValueGetter implements ValueGetter {

		private final Supplier<?> obj;

		public SupplierValueGetter(Supplier<?> obj) {
			this.obj = obj;
		}

		@Override
		public final Object get(Object target) {
			return obj.get();
		}

	}

	private static class FunctionValueGetter implements ValueGetter {

		private final Function<Object, ?> obj;

		public FunctionValueGetter(Function<Object, ?> obj) {
			this.obj = obj;
		}

		@Override
		public final Object get(Object target) {
			return obj.apply(target);
		}

	}

}
